/**
 * 项目名：化工企业安全生产信息化管理平台
 * 日期：    2020-06-28 02:01:44
 * Copyright (c) 2015- -版权所有
 */

package com.joysuch.wwyt.audit.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.common.collect.Lists;
import com.joysuch.wwyt.audit.bean.AuditLogRecordDetailBean;
import com.joysuch.wwyt.audit.bean.AuditLogRecordEditBean;
import com.joysuch.wwyt.audit.bean.AuditLogRecordListBean;
import com.joysuch.wwyt.audit.bean.AuditLogRecordListBean.SearchAuditLogRecord;
import com.joysuch.wwyt.audit.entity.AuditLogRecord;
import com.joysuch.wwyt.audit.entity.AuditLogRecordHis;
import com.joysuch.wwyt.audit.entity.dsl.QAuditLogRecord;
import com.joysuch.wwyt.audit.mapper.AuditLogRecordHisMapper;
import com.joysuch.wwyt.audit.mapper.AuditLogRecordMapper;
import com.joysuch.wwyt.audit.repository.AuditLogRecordDao;
import com.joysuch.wwyt.audit.repository.AuditLogRecordHisDao;
import com.joysuch.wwyt.audit.service.AuditLogRecordService;
import com.joysuch.wwyt.common.constant.Constant;
import com.joysuch.wwyt.config.RedisClient;
import com.joysuch.wwyt.core.common.ajax.ResultBean;
import com.joysuch.wwyt.core.common.util.HttpUtils;
import com.joysuch.wwyt.core.common.util.PageDataUtils;
import com.joysuch.wwyt.core.common.util.PageDataUtils.PageDataConvert;
import com.joysuch.wwyt.core.exception.BusinessRestrictionsException;
import com.joysuch.wwyt.core.service.BizCodeGeneratorFacade;
import com.joysuch.wwyt.core.support.Context;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.transaction.Transactional;
import java.util.Date;
import java.util.List;

@Slf4j
@Service
@Transactional
public class AuditLogRecordServiceImpl implements AuditLogRecordService {

    @Autowired
    private AuditLogRecordDao dao;

    @Autowired
    private AuditLogRecordHisDao auditLogRecordHisDao;

    @Autowired
    private BizCodeGeneratorFacade bizCodeGeneratorFacade;
    @Autowired
    private AuditLogRecordMapper auditLogRecordMapper;
    @Autowired
    private AuditLogRecordHisMapper auditLogRecordHisMapper;
    @Autowired
    private RedisClient redisClient;

    @Override
    public AuditLogRecord add(AuditLogRecordEditBean entity) {
        AuditLogRecord content = new AuditLogRecord();
        if (StringUtils.isNotBlank(entity.getCode())) {
            Assert.isTrue(dao.countOfCode(entity.getCode()) == 0, String.format("编号%s已经存在", entity.getCode()));
            content.setCode(entity.getCode());
        } else {
            content.setCode(String.valueOf(new Date().getTime()));
        }
        BeanUtils.copyProperties(entity, content, "id", "code");
        dao.save(content);
        return content;
    }

    @Override
    public void update(AuditLogRecordEditBean entity) {
        AuditLogRecord content = dao.findById(entity.getId()).get();
        Assert.notNull(entity.getCode(), "编号不能为空");
        Assert.isTrue(dao.countOfCodeExcludeId(entity.getCode(), entity.getId()) == 0,
                String.format("编号%s已经存在", entity.getCode()));
        content.setCode(entity.getCode());
        BeanUtils.copyProperties(entity, content, "id", "code");
        dao.save(content);
    }

    @Override
    public Page<AuditLogRecordListBean> findByPage(Pageable page, SearchAuditLogRecord condition) {
        Predicate predicate = prepareListSearchCondition(condition);

        Page<AuditLogRecord> data = predicate == null ? dao.findAll(page) : dao.findAll(predicate, page);

        return PageDataUtils.convertPageData(data, new PageDataConvert<AuditLogRecordListBean, AuditLogRecord>() {

            @Override
            public AuditLogRecordListBean convert(AuditLogRecord f) {
                AuditLogRecordListBean bean = new AuditLogRecordListBean();
                BeanUtils.copyProperties(f, bean);
                bean.setResultName(f.getResult() != null && f.getResult() == 0 ? "成功" : "失败");
                return bean;
            }

        });
    }

    @Override
    public ResultBean findByPageHis(Pageable pageable, AuditLogRecordListBean.SearchAuditLogHisRecord condition) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<AuditLogRecordListBean> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageable.getPageNumber() + 1, pageable.getPageSize());
        IPage<AuditLogRecordHis> recordListBeanIPage = auditLogRecordHisMapper.selectHisPage(page, condition);
        List<AuditLogRecordHis> records = recordListBeanIPage.getRecords();
        List<AuditLogRecordListBean> results = Lists.newArrayList();
        records.forEach(f -> {
            AuditLogRecordListBean bean = new AuditLogRecordListBean();
            BeanUtils.copyProperties(f, bean);
            bean.setResultName(f.getResult() != null && f.getResult() == 0 ? "成功" : "失败");
            results.add(bean);
        });
        return ResultBean.pageData(results, recordListBeanIPage.getTotal());
    }

    private Predicate prepareListSearchCondition(SearchAuditLogRecord condition) {
        Predicate p = null;
        QAuditLogRecord q = QAuditLogRecord.auditLogRecord;
        if (condition.getCode() != null) {
            p = ExpressionUtils.and(p, q.code.contains(condition.getCode()));
        }
        if (condition.getActionDefId() != null) {
            p = ExpressionUtils.and(p, q.actionDefId.eq(condition.getActionDefId()));
        }
        if (condition.getUserId() != null) {
            p = ExpressionUtils.and(p, q.userId.eq(condition.getUserId()));
        }
        if (condition.getUserName() != null) {
            p = ExpressionUtils.and(p, q.userName.contains(condition.getUserName()));
        }
        if (condition.getModule() != null) {
            p = ExpressionUtils.and(p, q.module.contains(condition.getModule()));
        }
        if (condition.getOperationName() != null) {
            p = ExpressionUtils.and(p, q.operationName.contains(condition.getOperationName()));
        }
        if (condition.getHappenTime() != null) {
            p = ExpressionUtils.and(p, q.happenTime.eq(condition.getHappenTime()));
        }
        if (condition.getIp() != null) {
            p = ExpressionUtils.and(p, q.ip.contains(condition.getIp()));
        }
        if (condition.getMac() != null) {
            p = ExpressionUtils.and(p, q.mac.eq(condition.getMac()));
        }
        if (condition.getResult() != null) {
            p = ExpressionUtils.and(p, q.result.eq(condition.getResult()));
        }
        if (condition.getOpObjectType() != null) {
            p = ExpressionUtils.and(p, q.opObjectType.eq(condition.getOpObjectType()));
        }
        if (condition.getOpObjectValue() != null) {
            p = ExpressionUtils.and(p, q.opObjectValue.eq(condition.getOpObjectValue()));
        }
        if (condition.getDescription() != null) {
            p = ExpressionUtils.and(p, q.description.contains(condition.getDescription()));
        }
        if (condition.getStartTime() != null) {
            p = ExpressionUtils.and(p, q.happenTime.goe(condition.getStartTime()));
        }
        if (condition.getEndTime() != null) {
            p = ExpressionUtils.and(p, q.happenTime.loe(condition.getEndTime()));
        }
        return p;
    }

    @Override
    public void delete(Long id) throws BusinessRestrictionsException {
        // 校验业务逻辑是否允许删除。如本数据被其他业务关联，删除后导致其他业务不可用。不能删除时将抛出业务异常
        checkBussinessRestrictions(id);
        dao.deleteById(id);
    }

    /**
     * 校验业务逻辑是否允许删除。如本数据被其他业务关联，删除后导致其他业务不可用
     *
     * @param id
     *            要删除的业务ID
     * @throws BussinessRestrictionsException
     *             抛出的业务异常
     */
    private void checkBussinessRestrictions(Long id) throws BusinessRestrictionsException {
    }

    @Override
    public AuditLogRecord findById(Long id) {
        return dao.findById(id).orElse(null);
    }

    @Override
    public AuditLogRecordDetailBean getById(Long id) {
        AuditLogRecord entity = findById(id);
        if (entity == null) {
            return null;
        }
        AuditLogRecordDetailBean bean = new AuditLogRecordDetailBean();
        BeanUtils.copyProperties(entity, bean);
        return bean;
    }

    @Override
    public void addAuditLog(String module, String operationName, int result, Class opeartionObjectType,
                            String operationObjectValue, String description) {
        AuditLogRecord r = new AuditLogRecord();
        r.setCode(String.valueOf(new Date().getTime()));
        r.setHappenTime(new Date());
        r.setIp(parseIp());
        r.setMac(null);
        r.setModule(module);
        r.setOperationName(operationName);
        // 判断执行状态
        r.setResult((byte) result);
        r.setUserId(Context.getCurrentUserId());
        if (Context.getCurrentUser() != null) {
            r.setUserName(Context.getCurrentUser().getRealName());
        }
        r.setOpObjectType(opeartionObjectType != null ? opeartionObjectType.getName() : null);
        r.setOpObjectValue(operationObjectValue);
        r.setDescription(description);
        dao.save(r);
    }

    private String parseIp() {
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        return HttpUtils.getClientIp(request);
    }

    /**
     * 每天凌晨3点~4点, 每七分钟执行一次, 迁移审计日志到his表,
     */
    @Scheduled(cron = "0 0/7 03-04 * * ? ")
    @Transactional
    @Override
    public void autoMoveAuditLogOnce() {
        // 当历史审计日志数据迁移完毕后, 才开始执行每日一次的迁移job,
        if (redisClient.get(Constant.MOVE_AUDIT_LOG_TO_HIS_OVER_FLAG) != null) {
            // 审计日志表只保留一年的数据
            Date endTime = DateUtil.offsetMonth(DateUtil.beginOfDay(new Date()), -12);
            // 每日审计日志迁移, 不限制条数, 每日最多也就几万条
            moveData(endTime);
        }
    }

    /**
     * 迁移审计日志到his表, 上线后7秒执行一次, 直到数据迁移完毕
     */
    @Scheduled(cron = "0/7 * * * * ?")
    @Transactional
    @Override
    public void autoMoveAuditLog() {
        if (redisClient.get(Constant.MOVE_AUDIT_LOG_TO_HIS_OVER_FLAG) != null) {
            return;
        }
        // 审计日志表只保留一年的数据
        Date endTime = DateUtil.offsetMonth(DateUtil.beginOfDay(new Date()), -12);
        // 历史审计日志迁移
        boolean over = moveData(endTime);
        if (over) {
            log.info("========== 历史审计日志已迁移完毕!");
            redisClient.set(Constant.MOVE_AUDIT_LOG_TO_HIS_OVER_FLAG, "1");
        }
    }

    @Transactional
    @Override
    public void handleMoveAuditLog(Date endTime) {
        moveData(endTime);
    }

    private boolean moveData(Date endTime) {
        StopWatch stopWatch = new StopWatch();
        List<Long> logIds = auditLogRecordMapper.getIdByCreateTime(endTime, true);
        if (CollectionUtil.isEmpty(logIds)) {
            return true;
        }
        List<List<Long>> logIdsList = Lists.partition(logIds, 1000);
        for (int i = 0; i < logIdsList.size(); i++) {
            List<Long> ids = logIdsList.get(i);
            stopWatch.start(String.format("第%d批迁移审计日志数据%d条", i + 1, ids.size()));
            int moveCount = auditLogRecordMapper.copyDataToHisByIds(ids);
            if (moveCount == ids.size()) {
                auditLogRecordMapper.deleteByIds(ids);
            }
            stopWatch.stop();
            log.info("========== {}, 耗时: {} ms", stopWatch.getLastTaskName(), stopWatch.getLastTaskTimeMillis());
        }
        log.info("========== 迁移审计日志, 总条数: {}, 总耗时: {} ms", logIds.size(), stopWatch.getTotalTimeMillis());
        return false;
    }

}
